import React, { useState } from "react";
import {
Alert,
Keyboard,
Linking,
Pressable,
ScrollView,
View,
} from "react-native";
import ImageView from "react-native-image-viewing";
import * as Haptics from "expo-haptics";
import { Stack, useLocalSearchParams, useRouter } from "expo-router";
import BookmarkAssetImage from "@/components/bookmarks/BookmarkAssetImage";
import {
BookmarkLinkArchivePreview,
BookmarkLinkBrowserPreview,
BookmarkLinkReaderPreview,
BookmarkLinkScreenshotPreview,
} from "@/components/bookmarks/BookmarkLinkPreview";
import BookmarkTextMarkdown from "@/components/bookmarks/BookmarkTextMarkdown";
import { PDFViewer } from "@/components/bookmarks/PDFViewer";
import FullPageError from "@/components/FullPageError";
import { TailwindResolver } from "@/components/TailwindResolver";
import { Button } from "@/components/ui/Button";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
import { Input } from "@/components/ui/Input";
import { useToast } from "@/components/ui/Toast";
import { useAssetUrl } from "@/lib/hooks";
import { api } from "@/lib/trpc";
import { MenuView } from "@react-native-menu/menu";
import {
ChevronDown,
ClipboardList,
Globe,
Info,
Tag,
Trash2,
} from "lucide-react-native";
import { useColorScheme } from "nativewind";
import {
useDeleteBookmark,
useUpdateBookmark,
} from "@karakeep/shared-react/hooks/bookmarks";
import { BookmarkTypes, ZBookmark } from "@karakeep/shared/types/bookmarks";
type BookmarkLinkType = "browser" | "reader" | "screenshot" | "archive";
function BookmarkLinkTypeSelector({
type,
onChange,
}: {
type: BookmarkLinkType;
onChange: (type: BookmarkLinkType) => void;
}) {
return (
{
Haptics.selectionAsync();
onChange(nativeEvent.event as BookmarkLinkType);
}}
actions={[
{
id: "reader",
title: "Reader",
state: type === "reader" ? "on" : undefined,
},
{
id: "browser",
title: "Browser",
state: type === "browser" ? "on" : undefined,
},
{
id: "screenshot",
title: "Screenshot",
state: type === "screenshot" ? "on" : undefined,
},
{
id: "archive",
title: "Archive",
state: type === "archive" ? "on" : undefined,
},
]}
shouldOpenOnLongPress={false}
>
Haptics.selectionAsync()} color="gray" />
);
}
function BottomActions({ bookmark }: { bookmark: ZBookmark }) {
const { toast } = useToast();
const router = useRouter();
const { mutate: deleteBookmark, isPending: isDeletionPending } =
useDeleteBookmark({
onSuccess: () => {
router.back();
toast({
message: "The bookmark has been deleted!",
showProgress: false,
});
},
onError: () => {
toast({
message: "Something went wrong",
variant: "destructive",
showProgress: false,
});
},
});
const deleteBookmarkAlert = () =>
Alert.alert(
"Delete bookmark?",
"Are you sure you want to delete this bookmark?",
[
{ text: "Cancel", style: "cancel" },
{
text: "Delete",
onPress: () => deleteBookmark({ bookmarkId: bookmark.id }),
style: "destructive",
},
],
);
const actions = [
{
id: "lists",
icon: (
}
/>
),
shouldRender: true,
onClick: () =>
router.push(`/dashboard/bookmarks/${bookmark.id}/manage_lists`),
disabled: false,
},
{
id: "tags",
icon: (
}
/>
),
shouldRender: true,
onClick: () =>
router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`),
disabled: false,
},
{
id: "open",
icon: (
}
/>
),
shouldRender: true,
onClick: () => router.push(`/dashboard/bookmarks/${bookmark.id}/info`),
disabled: false,
},
{
id: "delete",
icon: (
}
/>
),
shouldRender: true,
onClick: deleteBookmarkAlert,
disabled: isDeletionPending,
},
{
id: "browser",
icon: (
}
/>
),
shouldRender: bookmark.content.type == BookmarkTypes.LINK,
onClick: () =>
bookmark.content.type == BookmarkTypes.LINK &&
Linking.openURL(bookmark.content.url),
disabled: false,
},
];
return (
{actions.map(
(a) =>
a.shouldRender && (
{a.icon}
),
)}
);
}
function BookmarkLinkView({
bookmark,
bookmarkPreviewType,
}: {
bookmark: ZBookmark;
bookmarkPreviewType: BookmarkLinkType;
}) {
if (bookmark.content.type !== BookmarkTypes.LINK) {
throw new Error("Wrong content type rendered");
}
switch (bookmarkPreviewType) {
case "browser":
return ;
case "reader":
return ;
case "screenshot":
return ;
case "archive":
return ;
}
}
function BookmarkTextView({ bookmark }: { bookmark: ZBookmark }) {
if (bookmark.content.type !== BookmarkTypes.TEXT) {
throw new Error("Wrong content type rendered");
}
const { toast } = useToast();
const [isEditing, setIsEditing] = useState(false);
const initialText = bookmark.content.text;
const [content, setContent] = useState(initialText);
const { mutate, isPending } = useUpdateBookmark({
onError: () => {
toast({
message: "Something went wrong",
variant: "destructive",
});
},
onSuccess: () => {
setIsEditing(false);
},
});
return (
{isEditing && (
)}
{isEditing ? (
mutate({
bookmarkId: bookmark.id,
text: content,
})
}
value={content}
onChangeText={setContent}
multiline
autoFocus
/>
) : (
setIsEditing(true)}>
)}
);
}
function BookmarkAssetView({ bookmark }: { bookmark: ZBookmark }) {
const [imageZoom, setImageZoom] = useState(false);
if (bookmark.content.type !== BookmarkTypes.ASSET) {
throw new Error("Wrong content type rendered");
}
const assetSource = useAssetUrl(bookmark.content.assetId);
// Check if this is a PDF asset
if (bookmark.content.assetType === "pdf") {
return (
);
}
// Handle image assets as before
return (
setImageZoom(false)}
doubleTapToZoomEnabled={true}
images={[assetSource]}
/>
setImageZoom(true)}>
);
}
export default function ListView() {
const { slug } = useLocalSearchParams();
const { colorScheme } = useColorScheme();
const isDark = colorScheme === "dark";
const [bookmarkLinkType, setBookmarkLinkType] =
useState("reader");
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
const {
data: bookmark,
error,
refetch,
} = api.bookmarks.getBookmark.useQuery({
bookmarkId: slug,
includeContent: false,
});
if (error) {
return ;
}
if (!bookmark) {
return ;
}
let comp;
let title = null;
switch (bookmark.content.type) {
case BookmarkTypes.LINK:
title = bookmark.title ?? bookmark.content.title;
comp = (
);
break;
case BookmarkTypes.TEXT:
title = bookmark.title;
comp = ;
break;
case BookmarkTypes.ASSET:
title = bookmark.title ?? bookmark.content.fileName;
comp = ;
break;
}
return (
bookmark.content.type === BookmarkTypes.LINK ? (
setBookmarkLinkType(type)}
/>
) : undefined,
}}
/>
{comp}
);
}